package com.system.log;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.apache.commons.logging.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import com.system.exception.ApplicationException;


@Entity
@Table(name = "LOG_CORE_IMPL")
@SequenceGenerator(name = "logCoreImplIdGenerator", sequenceName = "SEQ_LOG_CORE_IMPL", allocationSize = 1)
public class LogCoreImpl implements Serializable {
	private static final long serialVersionUID = 598472916912119596L;

	public enum LogLevel {
		ERROR, WARN, INFO, DEBUG;
	}

	private Long id;
	private Date startTime;
	private Date endTime;
	private LogLevel logLevel;
	private String targetName;
	private String signatureName;
	private String arguments;
	private String returnValue;
	private Long result;
	private Throwable throwable;

	public void startLogging(ProceedingJoinPoint pjp) {
		this.setStartTime(new Date());
		Class targetClass = pjp.getTarget().getClass();
		this.setTargetName(targetClass.getSimpleName());
		Signature signature = pjp.getSignature();
		this.setSignatureName(signature.getName());
		this.setArguments(LogMethodExecutionHelper.getArguments(pjp));
	}

	public void logReturn(Object returnValue) {
		if (returnValue != null) {
			this.setReturnValue(returnValue.toString());
			this.setResult(LogMethodExecutionHelper.getResult(returnValue));
		}
		this.setLogLevel(LogLevel.INFO);
		this.setEndTime(new Date());
	}

	public void logThrowable(Throwable throwable) {
		LogLevel logLevel = null;
		if (throwable == null || throwable instanceof ApplicationException) {
			logLevel = LogLevel.INFO;
		} else if (throwable instanceof Error) {
			logLevel = LogLevel.ERROR;
		} else {
			logLevel = LogLevel.WARN;
		}
		this.setLogLevel(logLevel);
		this.setThrowable(throwable);
		this.setReturnValue(throwable.toString());
		this.setEndTime(new Date());
	}

	public void completeLogging(Log logger) {
		Throwable throwable = this.getThrowable();
		if (LogLevel.DEBUG.equals(this.getLogLevel())
				&& logger.isDebugEnabled()) {
			logger.debug(this.toString());
		} else if (LogLevel.INFO.equals(this.getLogLevel())
				&& logger.isInfoEnabled()) {
			logger.info(this.toString());
		} else if (LogLevel.WARN.equals(this.getLogLevel())
				&& logger.isWarnEnabled()) {
			if (throwable == null) {
				logger.warn(this.toString());
			} else {
				logger.warn(this.toString(), throwable);
			}
		} else if (LogLevel.ERROR.equals(this.getLogLevel())
				&& logger.isErrorEnabled()) {
			if (throwable == null) {
				logger.error(this.toString());
			} else {
				logger.error(this.toString(), throwable);
				throwable.printStackTrace();
			}
		}
	}

	public String toString() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		StringBuilder result = new StringBuilder();
		result.append("Start:");
		result.append(sdf.format(this.getStartTime()));
		result.append(",End: ");
		result.append(sdf.format(this.getEndTime()));
		result.append(" Execute ");
		result.append(this.getTargetName());
		result.append(".");
		result.append(this.getSignatureName());
		result.append("(");
		result.append(this.getArguments());
		result.append(")");
		if (this.getThrowable() != null) {
			result.append(" throw ");
			result.append(this.getThrowable());
		} else {
			result.append(" return ");
			result.append(this.getReturnValue());
		}
		return result.toString();
	}

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "logCoreImplIdGenerator")
	@Column(name = "ID", unique = true, nullable = false, length = 16)
	public Long getId() {
		return this.id;
	}

	private void setId(Long id) {
		this.id = id;
	}

	@Enumerated(EnumType.STRING)
	@Column(name = "LOG_LEVEL", nullable = false, length = 8)
	public LogLevel getLogLevel() {
		return this.logLevel;
	}

	public void setLogLevel(LogLevel logLevel) {
		this.logLevel = logLevel;
	}

	@Column(name = "TARGET_NAME", nullable = false, length = 256)
	public String getTargetName() {
		return this.targetName;
	}

	public void setTargetName(String targetName) {
		this.targetName = targetName;
	}

	@Column(name = "SIGNATURE_NAME", nullable = false, length = 256)
	public String getSignatureName() {
		return this.signatureName;
	}

	public void setSignatureName(String signatureName) {
		this.signatureName = signatureName;
	}

	@Column(name = "ARGUMENTS", length = 4000)
	public String getArguments() {
		return this.arguments;
	}

	private static final int STRING_MAX_LENGTH = 1280;

	public void setArguments(String arguments) {
		byte[] bytes = arguments.getBytes();
		if (bytes.length > STRING_MAX_LENGTH) {
			byte[] newBytes = new byte[STRING_MAX_LENGTH];
			System.arraycopy(bytes, 0, newBytes, 0, STRING_MAX_LENGTH);
			arguments = new String(newBytes) + "����";
		}
		this.arguments = arguments;
	}

	@Column(name = "RETURN_VALUE", length = 4000)
	public String getReturnValue() {
		return this.returnValue;
	}

	public void setReturnValue(String returnValue) {
		byte[] bytes = returnValue.getBytes();
		if (bytes.length > STRING_MAX_LENGTH) {
			byte[] newBytes = new byte[STRING_MAX_LENGTH];
			System.arraycopy(bytes, 0, newBytes, 0, STRING_MAX_LENGTH);
			returnValue = new String(newBytes) + "����";
		}
		this.returnValue = returnValue;
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "START_TIME", nullable = false, length = 7)
	public Date getStartTime() {
		return this.startTime;
	}

	public void setStartTime(Date startTime) {
		this.startTime = startTime;
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "END_TIME", nullable = false, length = 7)
	public Date getEndTime() {
		return this.endTime;
	}

	public void setEndTime(Date endTime) {
		this.endTime = endTime;
	}

	@Column(name = "RESULT", length = 16)
	public Long getResult() {
		return result;
	}

	public void setResult(Long result) {
		this.result = result;
	}

	@Transient
	public Throwable getThrowable() {
		return throwable;
	}

	public void setThrowable(Throwable throwable) {
		this.throwable = throwable;
	}
}
